#include"definicje.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include"protokol.h"
#include"crc32.h"
#include<syslog.h>

#define POWTARZAJ 20

/* Oblicza sumę kontrolną */
void obliczsume(uint8_t *d)
{
#ifdef DEBUG
   return;
#endif

   struct naglowek *nag=(struct naglowek *) d;
   nag->suma=htonl((uint32_t)0u);

   nag->suma = htonl(
        suma_crc32(d,rozmiarNaglowka+ntohs(nag->rozmiar)));

}


/* Sprawdza poprawność sumy kontrolnej */
bool czyPoprawny(uint8_t *d)
{
#ifdef DEBUG
   return true;
#endif

    struct naglowek *nag = (struct naglowek *) d;

    uint32_t suma_temp=nag->suma;

    nag->suma=htonl((uint32_t)0u);

    uint32_t suma = ntohl(
        suma_crc32(d,rozmiarNaglowka+htons(nag->rozmiar)));

    nag->suma = suma_temp;

    return suma==suma_temp;


}

/* Wysyła pakiet */
int wyslijpakiet(
    const int gniazdo,
    const struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint8_t * klucz,
    bool szyfrowane
    )
{
   ssize_t wyslane;
   size_t iloscBajtow;
   struct naglowek *n=(struct naglowek *)d;
   iloscBajtow=rozmiarNaglowka+ntohs(n->rozmiar);
   obliczsume(d);

   long int ileBajtow = (long int) iloscBajtow;
   uint8_t * dk = (uint8_t*)malloc(sizeof (uint8_t));
   //int err_comp = ezcompress(dk, &ileBajtow,d, ileBajtow);

    wyslane=sendto(gniazdo,d,iloscBajtow,0,(struct sockaddr *)adres,sizeof(struct sockaddr_storage));
   //wyslane=sendto(gniazdo,dk,ileBajtow,0,(struct sockaddr *)adres,sizeof(struct sockaddr_storage));
#ifdef DEBUG
   struct sockaddr_in *ain=(struct sockaddr_in *)adres;
   fprintf(stderr,"\nadres=%"PRIx32":%"PRIx16"\n",ntohl(ain->sin_addr.s_addr),ntohs(ain->sin_port));
#endif
   if(wyslane== -1)
   {
      perror("sendto");
      return -1;
   }
   if(wyslane < iloscBajtow)
   {
      fprintf(
	    stderr,
	    "Nie udało się wysłać pakietu o rozmiarze %zu\n",
	    iloscBajtow);
      return -1;
   }
   return 0;
}

/* Odbiera pakiet */
int odbierzpakiet(
    const int gniazdo,
    struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint8_t * klucz,
    bool szyfrowane
    )

{
   ssize_t odebrane;
   socklen_t r=sizeof(struct sockaddr_storage);

   struct naglowek *n=(struct naglowek *)d;
   int16_t iloscBajtow;
   long int ileBajtow = MAX_PAKIET;
   uint8_t *dk = (uint8_t*) malloc(MAX_PAKIET);
#ifdef DEBUG
   fprintf(stderr,"funkcja odbierzpakiet()\n");
   fprintf(stderr,"ustawiam pamiec\n");
#endif
   memset(adres,0u,sizeof(struct sockaddr_storage));


#ifdef DEBUG
   fprintf(stderr,"recvfrom()\n");
#endif
   odebrane=recvfrom(gniazdo,d,MAX_PAKIET,0,(struct sockaddr *)adres,&r);
   //int err_comp = ezuncompress(d, &ileBajtow,dk,ileBajtow);

#ifdef DEBUG
   struct sockaddr_in *ain=(struct sockaddr_in *)adres;
#ifdef DEBUG
   fprintf(stderr,"wypiska\n");
#endif
   fprintf(stderr,"\nadres=%"PRIx32":%"PRIx16"\n",ntohl(ain->sin_addr.s_addr),ntohs(ain->sin_port));
#endif
   if(odebrane==-1)
   {
      perror("recvfrom");
      return -1;
   }
   if(odebrane==0)
   {
      fprintf(
	    stderr,
	    "Serwer zamknął połączenie\n");
      return -1;
   }
   iloscBajtow=rozmiarNaglowka+ntohs(n->rozmiar);
   if(iloscBajtow != odebrane)
   {
      fprintf(
	    stderr,
	    "Ilosć bajtów odebranych nie zgadza się z rozmiarem "
	    "podanym w pakiecie\n");
      return -1;
   }
   if(iloscBajtow >= MAX_PAKIET)
   //if (ileBajtow >= MAX_PAKIET)
   {
      fprintf(
	    stderr,
	    "Rozmiar podany w pakiecie jest nieprawidłowy\n");
      return -1;
   }
   if((n->operacja==OP_ACK) && (iloscBajtow < rozmiarACK))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie potwierdzającym\n");
      return -1;
   }
   if((n->operacja==OP_ERR) && (iloscBajtow < rozmiarACK))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie oznaczającym błąd\n");
      return -1;
   }
   if((n->operacja==OP_SET) && (iloscBajtow < rozmiarSET))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie ustawiającym opcje %zu\n",rozmiarSET);
      return -1;
   }
   if((n->operacja==OP_CERT) && (iloscBajtow < rozmiarCERT))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie z certyfikatem %zu\n",rozmiarCERT);
      return -1;
   }
   if((n->operacja==OP_KEY) && (iloscBajtow < rozmiarKEY))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie z certyfikatem %zu\n",rozmiarKEY);
      return -1;
   }
   if(!czyPoprawny(d))
   {
      fprintf(
	    stderr,
	    "Błędna suma kontrolna pakietu\n");
      return -1;
   }
   if(!poprawnaOperacja(n->operacja))
   {
      fprintf(
	    stderr,
	    "Niepoprawna operacja protokołu\n");
      return -1;
   }
   return 0;

}

/* Wysyła dopóki nie odbierze prawidłowego potwierdzenia */
int wyslijzpotw(
    const int gniazdo,
    const struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint8_t * const dp,
    uint32_t * const ktory,
    uint32_t * const ktoryTam,
    uint8_t * klucz,
    bool szyfrowane
    )
{
   struct sockaddr_storage adres2;
   int wynik;
   unsigned int i=0;

   memset(&adres2,(int8_t)0u,sizeof(struct sockaddr_storage));
   struct naglowek *n=(struct naglowek *)d;
   struct pakietACK *p=(struct pakietACK *)dp;

   do
   {
      if(i>=POWTARZAJ)
      {
	 fprintf(stderr,"Przekroczono ilość błędnych pakietów\n");
	 return -1;
      }
      ++i;
      wynik=wyslijpakiet(gniazdo,adres,d, klucz, szyfrowane);
      if(wynik==-1)
      {
	 return -1;
      }
      wynik=odbierzpakiet(gniazdo,&adres2,dp, klucz, szyfrowane);
      if(wynik==-1)
      {
	 continue;
      }
#ifdef DEBUG
	 if(p->naglowek.operacja!=OP_ACK)
	    fprintf(stderr,"blad1\n");
	 if(p->naglowek.ktory!=htonl(*ktoryTam))
	    fprintf(stderr,"blad2\n");
	 if (p->ktory!=n->ktory)
	    fprintf(stderr,"blad3\n");
	 if(memcmp(adres,&adres2,sizeof(struct sockaddr_storage))!=0)
	    fprintf(stderr,"blad4\n");
#endif
   } while(
	 (p->naglowek.operacja!=OP_ACK) ||
	 (p->naglowek.ktory!=htonl(*ktoryTam)) ||
	 (p->ktory!=n->ktory) ||
	 (memcmp(adres,&adres2,sizeof(struct sockaddr_storage))!=0)
	 );
   ++(*ktory);
   ++(*ktoryTam);
   return 0;
}


/* Odbiera i wysyła potwierdzenie */
int odbierzzpotw(
    const int gniazdo,
    struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint32_t * const ktory,
    uint32_t * const ktoryTam,
    uint8_t * klucz,
    bool szyfrowane
    )
{
   struct pakietACK p;
   int wynik;
   unsigned int i=0;
   struct naglowek *n=(struct naglowek *)d;

   p.naglowek.rozmiar=htons((uint16_t)4u);
   p.naglowek.opcje=(uint8_t)0u;
   p.naglowek.ktory=htonl(*ktory);
   p.ktory=htonl(*ktoryTam);

   do
   {
      if(i>=POWTARZAJ)
      {
	 fprintf(stderr,"Przekroczono ilość błędnych pakietów\n");
	 return -1;
      }
      ++i;
      wynik=odbierzpakiet(gniazdo,adres,d, klucz, szyfrowane);
      if(wynik==-1 || (n->ktory!=htonl(*ktoryTam)))
      {
#ifdef DEBUG
	 fprintf(stderr,"xblad\n");
#endif
	 p.naglowek.operacja=OP_ERR;
	 wynik=wyslijpakiet(gniazdo,adres,(int8_t *)&p, klucz, szyfrowane);
	 if(wynik==-1)
	 {
	    return -1;
	 }
	 continue;
      }
      else
      {
	 break;
      }

   } while(true);
   p.naglowek.operacja=OP_ACK;
   wynik=wyslijpakiet(gniazdo,adres,(int8_t *)&p, klucz, szyfrowane);
   if(wynik==-1)
   {
	 return -1;
   }
   ++(*ktory);
   ++(*ktoryTam);
}


void piszsyslog(char tekst[], bool klient)
{
    setlogmask (LOG_UPTO (LOG_NOTICE));
    if(klient)
    openlog ("TSK_klient", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
    else
    openlog ("TSK_server", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);

    syslog (LOG_NOTICE, tekst);

    closelog();
}
